﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace ExpressionMapper
{
    using MappingExpressionGenerator = Func<Expression, Type, Expression, ParameterExpression, Expression>;

    /// <summary>
    /// Returns generator for Dictionary to Dictionary mappings.
    /// </summary>
    public static partial class Generators
    {
        public static MappingExpressionGenerator StandartCollectionToStandartCollection()
        {
            return (fromExpr, to, customMapping, referenceDictionary) =>
                   (IsEnumerable(fromExpr.Type) && IsStandardCollection(to))
                       ? customMapping ?? GenerateStandartCollectionExpression(fromExpr, to, referenceDictionary)
                       : null;
        }

        private static Expression GenerateStandartCollectionExpression(Expression fromExpr,
                                                               Type to, ParameterExpression referenceDictionary)
        {
            Debug.WriteLine("\tEnumerableToStandartCollection ({0}): {1} to {2}", fromExpr.NodeType, fromExpr.Type.Name,
                                                 to.Name);

            Type fromElementType = GetCollectionElementType(fromExpr.Type);
            Type toElementType = GetCollectionElementType(to);

            string methodName = null;
            if (IsStandartObjectCollection(to))
            {
                methodName = "Convert" + to.Name;
            }
            if (IsStandartGenericCollection(to))
            {
                methodName = "ConvertG" + to.Name.Substring(0, to.Name.Length-2);
            }

            if (string.IsNullOrEmpty(methodName))
                throw new InvalidOperationException(string.Format("Could not find conversion method for type {0}", to.FullName));

            MethodInfo convertAllMethod = typeof(Generators)
                .GetMethod(methodName, BindingFlags.Static | BindingFlags.Public)
                .MakeGenericMethod(fromElementType, toElementType);

            return Expression.Call(convertAllMethod, fromExpr, referenceDictionary);
        }


        private static bool IsStandardCollection(Type t)
        {
            return IsStandartObjectCollection(t) || IsStandartGenericCollection(t);
        }

        #region StandartGenericCollection

        private static bool IsStandartGenericCollection(Type type)
        {
            if (type.IsGenericType && (
                type.GetGenericTypeDefinition() == typeof(HashSet<>) ||
                type.GetGenericTypeDefinition() == typeof(LinkedList<>) ||
                type.GetGenericTypeDefinition() == typeof(Queue<>) ||
                type.GetGenericTypeDefinition() == typeof(SortedSet<>)))
                return true;

            return false;
        }

        public static HashSet<TOutput> ConvertGHashSet<TInput, TOutput>(IEnumerable<TInput> collection, ReferenceDictionary referenceDictionary)
        {
            if (collection == null)
                return null;

            HashSet<TOutput> result = new HashSet<TOutput>();
            foreach (var item in collection)
            {
                result.Add(Mapper.Map<TInput, TOutput>(item, referenceDictionary));
            }
            return result;
        }

        public static LinkedList<TOutput> ConvertGLinkedList<TInput, TOutput>(IEnumerable<TInput> collection, ReferenceDictionary referenceDictionary)
        {
            if (collection == null)
                return null;

            LinkedList<TOutput> result = new LinkedList<TOutput>();
            foreach (var item in collection)
            {
                result.AddLast(Mapper.Map<TInput, TOutput>(item, referenceDictionary));
            }
            return result;
        }

        public static Queue<TOutput> ConvertGQueue<TInput, TOutput>(IEnumerable<TInput> collection, ReferenceDictionary referenceDictionary)
        {
            if (collection == null)
                return null;

            Queue<TOutput> result = new Queue<TOutput>();
            foreach (var item in collection)
            {
                result.Enqueue(Mapper.Map<TInput, TOutput>(item, referenceDictionary));
            }
            return result;
        }

        public static SortedSet<TOutput> ConvertGSortedSet<TInput, TOutput>(IEnumerable<TInput> collection, ReferenceDictionary referenceDictionary)
        {
            if (collection == null)
                return null;

            SortedSet<TOutput> result = new SortedSet<TOutput>();
            foreach (var item in collection)
            {
                result.Add(Mapper.Map<TInput, TOutput>(item, referenceDictionary));
            }
            return result;
        }

        #endregion StandartGenericCollection

        #region StandartObjectCollection

        private static bool IsStandartObjectCollection(Type type)
        {
            if (type == typeof(ArrayList) ||
                type == typeof(BitArray) ||
                type == typeof(Queue))
                return true;

            return false;
        }

        public static ArrayList ConvertArrayList<TInput, TOutput>(IEnumerable<TInput> collection, ReferenceDictionary referenceDictionary)
        {
            if (collection == null)
                return null;

            ArrayList result = new ArrayList();
            foreach (var item in collection)
            {
                result.Add(Mapper.Map<TInput, TOutput>(item, referenceDictionary));
            }
            return result;
        }

        public static BitArray ConvertBitArray<TInput, TOutput>(IEnumerable<TInput> collection, ReferenceDictionary referenceDictionary)
        {
            if (collection == null)
                return null;

            return new BitArray(collection.Select(i => Mapper.Map<TInput, bool>(i, referenceDictionary)).ToArray());
        }

        public static Queue ConvertQueue<TInput, TOutput>(IEnumerable<TInput> collection, ReferenceDictionary referenceDictionary)
        {
            if (collection == null)
                return null;

            Queue result = new Queue();
            foreach (var item in collection)
            {
                result.Enqueue(Mapper.Map<TInput, TOutput>(item, referenceDictionary));
            }
            return result;
        }

        #endregion StandartObjectCollection
    }
}
